package com.ztom.agent.asm;

import com.ztom.agent.bean.InjectClassInfo;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

/**
 * @author ZhangTao
 */
public abstract class BaseAdviceAdapter extends AdviceAdapter {

    private static final Type THROWABLE_TYPE = Type.getType(Throwable.class);

    private Label methodStartLabel;

    private Label endFinallyLabel;

    protected InjectClassInfo injectClassInfo;

    public BaseAdviceAdapter(MethodVisitor mv, int access, String name, String desc, InjectClassInfo injectClassInfo) {
        super(ASM5, mv, access, name, desc);
        methodStartLabel = new Label();
        endFinallyLabel = new Label();
        this.injectClassInfo = injectClassInfo;
    }

    /**
     * 加载入口注入的方法的参数
     */
    public abstract void loadArgsOnMethodBegin();

    /**
     * 加载出口注入的方法的参数
     */
    public abstract void loadArgsOnMethodEnd();

    /**
     * 加载异常退出处注入的方法的参数
     */
    public abstract void loadArgsOnThrow();

    public void beforeMethodEnter() {

    }

    public void afterMethodEnter() {

    }

    public void beforeMethodExit() {

    }

    public void afterMethodExit() {

    }

    @Override
    protected void onMethodEnter() {
        beforeMethodEnter();

        // 将此处字节码地址标记为方法开始
        visitLabel(methodStartLabel);
        // 加载入口注入的方法的参数
        loadArgsOnMethodBegin();
        // 执行入口注入的方法
        invokeOnBeforeMethod();

        afterMethodEnter();
    }

    @Override
    protected void onMethodExit(int opcode) {
        if (opcode != ATHROW) {
            exitNormal();
        }
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {

        visitTryCatchBlock(methodStartLabel, endFinallyLabel, endFinallyLabel, THROWABLE_TYPE.getInternalName());
        visitLabel(endFinallyLabel);

        exitThrow();
        super.visitMaxs(maxStack, maxLocals);
    }

    private void exitThrow() {
        // 加载异常处注入的方法的参数
        loadArgsOnThrow();
        // 执行异常注入的方法
        invokeOnAfterMethod();
        // 将未捕获的继承继续抛出
        visitInsn(ATHROW);
    }

    private void exitNormal() {
        // 加载正常退出处注入的方法的参数
        loadArgsOnMethodEnd();
        // 执行正常退出注入的方法
        invokeOnAfterMethod();
    }

    public void invokeOnBeforeMethod() {
        invokeStatic(Type.getType(this.getClass()), Method.getMethod(injectClassInfo.getInjectBeginMethod()));
    }

    public void invokeOnAfterMethod() {
        invokeStatic(Type.getType(this.getClass()), Method.getMethod(injectClassInfo.getInjectEndMethod()));
    }
}
